
#include <stdio.h>
#include <time.h>

#include <BRange>
#include <BStyle>
#include <BFont>
#include <BEvent>
#include <BPainter>
#include <BKeyEvent>
#include <BCharEvent>
#include <BMouseEvent>
#include <BFocusEvent>
#include <BChangeEvent>

#include <BTimeEdit>
#include "member_BTimeEdit.h"

using namespace BWE;

#define member					(*(member_BTimeEdit*)_ptr)
#define member_allocate()		_ptr = new member_BTimeEdit(this)
#define member_release()		delete (member_BTimeEdit*)_ptr

BTimeEdit::BTimeEdit(BWidget* parent) : BWidget(parent)
{
	member_allocate();
}
BTimeEdit::BTimeEdit(const BTime& time, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->setTime(time);
}
BTimeEdit::~BTimeEdit()
{
	member_release();
}

void BTimeEdit::setTime(const BTime& time)
{
	if (member.time != time)
	{
		member.time = time;
		member.dirty = true;
		emit(Signal_TimeChanged, member.time);
		this->fresh();
	}
}
const BTime& BTimeEdit::time() const
{
	return member.time;
}

void BTimeEdit::setDisplayFormat(const BString& displayFormat)
{
	if (member.format != displayFormat)
	{
		member.format = displayFormat;
		member.format.lower();
		member.dirty = true;
		this->fresh();
	}
}
const BString& BTimeEdit::displayFormat() const
{
	return member.format;
}

void BTimeEdit::setEditable(bool editable)
{
	if (member.editable != editable)
	{
		member.editable = editable;
		this->fresh();
	}
}
bool BTimeEdit::editable() const
{
	return member.editable;
}

void BTimeEdit::setSyncCurrent(bool syncCurrent)
{
	if (member.syncCurrent != syncCurrent)
	{
		member.syncCurrent = syncCurrent;
		this->fresh();
	}
}
bool BTimeEdit::syncCurrent() const
{
	return member.syncCurrent;
}

BSize BTimeEdit::sizeHint() const
{
	BSize hintSize;
	const BFont* font = this->font();
	if (font)
	{
		int cw = font->codeWidth('0');
		hintSize.width() = cw * 8;
		hintSize.height() = font->height() + 3;
	}
	return this->shellSize() + hintSize;
	return this->shellSize() + hintSize;
}

void BTimeEdit::updateEvent(const BEvent& event)
{
	if (member.syncCurrent)
	{
		time_t t;
		::time(&t);
		tm* ptm = localtime(&t);
		BTime currentTime(ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
		this->setTime(currentTime);
	}
	if (this->focus() && member.inputCount == 0)
	{
		static clock_t tick = 0;
		clock_t now = clock();
		if (now - tick > 500)
		{
			tick = now;
			member.inputShown = !member.inputShown;
			this->fresh();
		}
		if (tick > now)
			tick = 0;
	}
}

void BTimeEdit::styleEvent(const BEvent& changeEvent)
{
	BStyle* style = this->realStyle();
	if (style)
	{
		const BFont* font = style->font();
		if (font)
			member.textHeight = font->size().height();
	}
}
void BTimeEdit::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.text = member.format;
		if (member.text.contain("hh"))
		{
			BString text = member.time.hours();
			if (text.size() < 2)
				text.prepend('0');
			member.text.replace("hh", text);
		}
		if (member.text.contain("mm"))
		{
			BString text = member.time.minutes();
			if (text.size() < 2)
				text.prepend('0');
			member.text.replace("mm", text);
		}
		if (member.text.contain("ss"))
		{
			BString text = member.time.seconds();
			if (text.size() == 1)
				text.prepend("0");
			member.text.replace("ss", text);
		}
		member.dirty = false;
	}
	member.freshRects();
}
void BTimeEdit::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();
	painter.drawContent();

	if (this->focus() && member.inputCount > 0)
	{
		int left = member.rects[member.inputIndex].x();
		int right = member.rects[member.inputIndex + member.inputCount].x();
		painter.setColor(Color_Background_Checked);
		painter.fillRect(left, member.textRect.top(), right - left, member.textRect.height());
	}

	if (painter.setColor(Color_Text))
	{
		painter.drawText(member.textRect, member.text);
	}

	painter.drawBorder();
	painter.drawPerch();
	painter.drawMargin();

	if (this->focus() && member.inputShown && member.inputCount == 0)
	{
		const BRect& rect = member.rects[member.inputIndex];
		int x = rect.left();
		int y = rect.center().y();
		int hh = member.textHeight / 2;
		if (painter.setColor(Color_Indicator))
		{
			painter.drawLine(x, y - hh, x, y + hh);
			painter.drawLine(x - 1, y - hh, x + 2, y - hh);
			painter.drawLine(x - 1, y + hh, x + 2, y + hh);
		}
	}

	int hoverIndex = member.hoverIndex;
	if (0)//hoverIndex >= 0)
	{
		const BRect& rect = member.rects[member.hoverIndex];
		int x = rect.left();
		int y = rect.center().y();
		int hh = member.textRect.height() * 0.6;

		painter.setLineWidth(1);
		painter.setColor(0, 255, 100);
		painter.drawLine(x, y - hh, x, y + hh);
	}
}

void BTimeEdit::focusEvent(const BFocusEvent& focusEvent)
{
	if (member.editable && focusEvent.focus() == Focus_In)
	{
		member.justFocused = true;
		member.inputIndex = 0;
		member.inputCount = member.text.count();
		this->fresh();
	}
	if (member.editable && focusEvent.focus() == Focus_Out)
	{
		if (member.changed)
		{
			BTime time;
			time.set(member.text);
			member.time = time;
			emit(Signal_EditFinished, member.text);
			member.changed = false;
		}
		this->fresh();
	}
}
void BTimeEdit::keyDownEvent(const BKeyEvent& keyEvent)
{
	Key key = keyEvent.key();
	switch (key)
	{
	case Key_Left:
	{
		if (member.inputCount > 0)
		{
			member.inputCount = 0;
			this->fresh();
		}
		else if (member.inputIndex > 0)
		{
			member.inputIndex--;
			this->fresh();
		}
		break;
	}
	case Key_Right:
	{
		if (member.inputCount > 0)
		{
			member.inputIndex += member.inputCount;
			member.inputCount = 0;
			this->fresh();
		}
		else if (member.inputIndex < member.text.size())
		{
			member.inputIndex++;
			this->fresh();
		}
		break;
	}
	case Key_Back:
	{
		if (member.inputCount > 0)
		{
			if (member.text.remove(member.inputIndex, member.inputCount))
			{
				member.inputCount = 0;
				member.changed = true;
				this->fresh();
				emit(Signal_Edited, member.text);
			}
		}
		else
		{
			member.inputIndex--;
			if (member.text.remove(member.inputIndex))
			{
				member.changed = true;
				this->fresh();
				emit(Signal_Edited, member.text);
			}
		}
		break;
	}
	case Key_Delete:
	{
		if (member.inputCount > 0)
		{
			if (member.text.remove(member.inputIndex, member.inputCount))
			{
				member.inputCount = 0;
				member.changed = true;
				this->fresh();
				emit(Signal_Edited, member.text);
			}
		}
		else
		{
			if (member.text.remove(member.inputIndex))
			{
				member.changed = true;
				this->fresh();
				emit(Signal_Edited, member.text);
			}
		}
		break;
	}
	case Key_Enter:
	{
		this->setFocus(false);
		break;
	}
	}
}
void BTimeEdit::keyUpEvent(const BKeyEvent& keyEvent)
{
	this->fresh();
}
void BTimeEdit::charEvent(const BCharEvent& charEvent)
{
	BCode code = charEvent.code();
	if (code == ':' || (code >= '0' && code <= '9'))
	{
		if (member.inputCount > 0)
		{
			member.text.remove(member.inputIndex, member.inputCount);
			member.inputCount = 0;
		}
		if (member.text.insert(member.inputIndex, code))
		{
			member.inputIndex++;
			member.changed = true;
			this->fresh();
			emit(Signal_Edited, member.text);
			this->fresh();
		}
	}
}

void BTimeEdit::mouseEnterEvent(const BMouseEvent& event)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
		this->fresh();
	}
}
void BTimeEdit::mouseLeaveEvent(const BMouseEvent& event)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
		this->fresh();
	}
}

void BTimeEdit::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (member.justFocused)
	{
		member.justFocused = false;
		this->fresh();
		return;
	}
	if (mouseEvent.button() == Button_Left)
	{
		const BPoint& pos = mouseEvent.pos();
		int hoverIndex = member.hoverIndex;
		if (member.inputCount > 0 || member.inputIndex != member.hoverIndex)
		{
			member.inputIndex = member.hoverIndex;
			member.inputCount = 0;
			this->fresh();
		}
		member.pressed = true;
		member.pressedIndex = member.hoverIndex;
	}
}
void BTimeEdit::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	member.pressed = false;
	this->fresh();
}
void BTimeEdit::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	int index = 0;
	while (index < member.rects.size())
	{
		const BRect& rect = member.rects[index];
		if (rect.center().x() > pos.x())
			break;
		index++;
	}
	if (member.hoverIndex != index)
	{
		member.hoverIndex = index;
		this->fresh();
	}
	if (member.pressed)
	{
		member.inputIndex = bMin(member.pressedIndex, member.hoverIndex);
		member.inputCount = abs(member.pressedIndex - member.hoverIndex);
		this->fresh();
	}
}
void BTimeEdit::mouseWheelEvent(const BMouseEvent& wheelEvent)
{
	int whell = wheelEvent.wheel();
	member.time += whell;
	emit(Signal_TimeChanged, member.time);
	emit(Signal_Edited, member.time);
	this->fresh();
}
void BTimeEdit::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex >= 0)
	{
		if (member.text[member.hoverIndex] == ':')
		{
			member.inputIndex = member.hoverIndex;
			member.inputCount = 1;
			this->fresh();
		}
		else
		{
			int begin = member.text.rfind(':', member.hoverIndex) + 1;
			int end = member.text.find(':', member.hoverIndex);
			member.inputIndex = begin;
			member.inputCount = end - begin;
			this->fresh();
		}
	}
}

